# -*- coding: utf-8 -*-

import os
import sys

if __name__ == "__main__":
    currentdir = os.path.dirname( os.path.abspath( sys.argv[0] ) )
    basedir = os.path.abspath( os.path.join( currentdir, ".." ) )
    pythondir = basedir
    sys.path.insert( 0, pythondir )
    pass

from logger import get_file_logger
from datetime import timedelta
from picklerpc.msghandler import RequestHandler
from picklerpc.sendmsg import sendmsg
from picklerpc import Server
from time import sleep, time
import traceback
import mimetypes
from service import default_request_timeout, default_niceness_level

__all__ = ['CheckFileHandler', 'CheckFileServer', 'CheckFileRequest', 'CheckFileManager']

#---------------------------------------------

class CheckFileHandler(RequestHandler):
    """
    Do some tasks, maybe in subprocess
    """
    
    def _request_handle(self, data):
        """
        @param data: simple object data, after json.loads
        @type: mixed
        
        @return: python simple object data, jsonable via json.dumps
        @rtype: mixed
        """
        result = False
        
        if not (type(data) is dict):
            return result

        if data["task"] == "ping":
            return self.server.ping()
        if data["task"] == "quit":
           return self.server.quit()
        if data["task"] == "check":
            data = data["data"]
            return self.server.check(
                data["dir_id"],
                data["parent_path"],
                data["name"]
            )
        return result
    
    pass

#---------------------------------------------

class CheckFileServer(Server):
    
    queue = None
    stop_event = None
    task_service_address = None
    _worker_thread = None

    log = None

    def ping(self):
        return "pong"


    def get_mime_type( self, full_path ):
        mime = mimetypes.guess_type( full_path )[0]
        if not mime:
            mime = "application/octet-stream"
        return mime


    def _worker ( self ):

        self.log.info("START: %s" % (self.server_address,))

        import json
        import fnmatch
        from archive import is_supported
        from service.task import TaskManager

        mimetypes.init()
        mimetypes.add_type( "application/x-rar", ".rar" )
        mimetypes.add_type( "application/x-rar", ".cbr" )
        mimetypes.add_type( "application/zip", ".cbz" )
        mimetypes.add_type( "application/x-7z-compressed", ".7z" )
        mimetypes.add_type( "application/x-7z-compressed", ".cb7" )
        mimetypes.add_type( "application/x-tar", ".cbt" )
        mimetypes.add_type( "application/x-tar", ".cbg" )
        mimetypes.add_type( "application/x-tar", ".cbb" )


        task_manager = TaskManager(self.task_service_address)

        stats_req = task_manager.get_request().getStatistics()
        hash_req = task_manager.get_request().getHash()
        archive_req = task_manager.get_request().getProcessArchive()
        db_req = task_manager.get_request().getDatabase()

        filter_in = db_req.query("get_option", ("filter_in",))
        self.log.debug("filter_in=%r" % (filter_in,))
        if filter_in:
            filter_in = json.loads(filter_in)
        else:
            filter_in = []

        filter_out = db_req.query("get_option", ("filter_out",))
        self.log.debug("filter_out=%r" % (filter_out,))
        if filter_out:
            filter_out = json.loads(filter_out)
        else:
            filter_out = []

        while not self.stop_event.isSet():

            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                sleep(0.1)
                continue


            parent_id, parent_path, name = data

            full_path = os.path.join(parent_path, name)
            if not os.path.isfile(full_path):
                self.queue.task_done()
                continue

            stats_req.inc("files_count", 1)

            match = False
            lpath = full_path.lower()
            for ptrn in filter_in:
                if fnmatch.fnmatchcase(lpath, ptrn.lower()):
                    match = True
                    break
            for ptrn in filter_out:
                if fnmatch.fnmatchcase(lpath, ptrn.lower()):
                    match = False
                    break

            if match:

                try:
                    st = os.lstat( full_path )

                    fsize = st.st_size
                    mtime = st.st_mtime
                    mime = self.get_mime_type( full_path )
                    file_id = db_req.query("insert_file", ( parent_id, st.st_dev, mime, fsize, mtime, name ))

                    stats_req.inc("files_size", fsize )

                    stats_req.inc("files_filtered", 1)

                    hash_req.hash( file_id, st.st_dev, fsize, full_path )

                    if is_supported(mime):
                        stats_req.inc("archives_count", 1)
                        stats_req.inc("archives_size", fsize)
                        archive_req.process( file_id, full_path, mime, fsize )

                except Exception as e:
                    stats_req.inc("errors_count", 1)
                    self.log.error("ERROR! %s" % (e,))
                    self.log.debug("ERROR! %s\nfile='%s'\n%s" % (
                        e, full_path, traceback.format_exc(), ))
                    pass

            self.queue.task_done()

        self.log.info("STOP: %s" % (self.server_address,))

        self.stop_event.clear()
        return

    def server_activate(self):
        try:
            # Python 3
            from queue import Queue
        except ImportError:
            # Python 2
            from Queue import Queue

        from threading import Thread, Event

        self.log = get_file_logger("CheckFileServer")

        self.queue = Queue()
        self.stop_event = Event()
        self._worker_thread = Thread(target=self._worker)
        self._worker_thread.start()
        return Server.server_activate(self)

    def check(self, dir_id, parent_path, name):
        self.queue.put_nowait((dir_id, parent_path, name,))
        return True


    def quit(self):
        self.log.info("quit event")

        import signal

        self.log.debug("send SIGINT to self")
        os.kill( os.getpid(), signal.SIGINT )

        return True


    def terminate_self(self, signum, frame):
        self.log.debug("got SHUTDOWN signal")
        t_begin = time()

        # Clean Queue

        while True:
            try:
                data = self.queue.get_nowait()
            except Exception:
                data = None

            if data is None:
                break

            self.queue.task_done()


        self.stop_event.set()
        self.stop_event.wait()
        self.server_close()
        t_end = time()
        self.log.info("SHUTDOWN in %s sec" % (timedelta(seconds=t_end - t_begin),))
        return

    pass


#---------------------------------------------

class CheckFileRequest(object):

    manager = None

    def __init__(self, manager):
        self.manager = manager
        pass

    def check(self, device, dir_id, parent_path, name):
        return self.manager.sendmsg( device, "check", {
            "dir_id" : dir_id,
            "parent_path" : parent_path,
            "name" : name
        })

    def ping(self, device):
        return self.manager.sendmsg(device, "ping", {}, 0.1)

    def ping_all(self):
        return self.manager.ping_all()

    def quit(self):
        return self.manager.quit_all()

    pass

#---------------------------------------------

class CheckFileManager(object):

    host = "127.0.1.5"
    port_min = 10000
    port_max = 20000
    ports = None
    request = None

    task_service_address = None

    log = None

    def __init__(self, task_service_address):
        self.ports = {}
        self.task_service_address = task_service_address
        self.log = get_file_logger("CheckFileManager")
        pass

    def start_service(self, device):

        device = str(device)

        runing = False
        if self.ports.get(device) is None:

            import random

            port = random.randint(self.port_min, self.port_max)

            self.ports[device] = port

            from archive import which
            from subprocess import Popen

            Popen([which("python3"), "service/check_file.py",
                   "--host", "%s" % self.host,
                   "--port", "%s" % port,
                   "--task-service", "%s:%s" % self.task_service_address
            ])

            # WAIT WHILE SERVICE STARTS

            pings = 100
            t_begin = time()
            while not runing:

                pong = self.get_request().ping(device)

                if pong == "pong":
                    runing = True

                pings -= 1
                if pings <= 0:
                    break

            if pings <= 0:
                del self.ports[device]
                self.log.error("FAIL TO START service on (%s, %s) in %s sec" % (
                    self.host, port, timedelta(seconds=time() - t_begin),))
            else:
                self.log.info("STARTED service on (%s, %s) in %s sec" % (
                    self.host, port, timedelta(seconds=time() - t_begin),))

        return runing

    def sendmsg(self, device, task, data, timeout=default_request_timeout):
        device = str(device)

        port = self.ports[device]
        SERVER_ADDRESS = (self.host, port,)

        try:
            result = sendmsg( SERVER_ADDRESS, {"task":task, "data":data}, timeout=timeout )
        except Exception as e:
            result = None
            self.log.error("SOCKET %r" % (e,))
            self.log.debug("FAIL SEND MESSAGE: task=%s, data=%r" % (task, data,))
        return result

    def quit_all(self):
        result = True
        ports = self.ports.copy()
        for device, port in ports.items():
            ret = self.sendmsg(device, "quit", {}, 0.1)
            if ret is None:
                result = False
            else:
                del self.ports[ device ]
        return result

    def ping_all(self):
        result = False
        ports = self.ports.copy()
        for device, port in ports.items():
            ret = self.get_request().ping(device)
            if ret == "pong":
                result = True
        return result

    def get_request(self):
        if not self.request:
            self.request = CheckFileRequest(self)
        return self.request

    pass


#---------------------------------------------

def run_service(host, port, task_service_address):

    if hasattr(os, "nice"):
        os.nice(default_niceness_level)

    server = None
    e_code = 0
    try:
        SERVER_ADDRESS = (host, int(port),)

        server = CheckFileServer(SERVER_ADDRESS, CheckFileHandler, bind_and_activate=False)
        if task_service_address:
            host, port = task_service_address.split(':')
            server.task_service_address = (host, int(port),)

        import signal

        signal.signal(signal.SIGINT, server.terminate_self)

        server.server_bind()
        server.server_activate()
        server.serve_forever()
    except Exception as e:
        e_code = 1
        if server and server.log:
            server.log.error("run_service: ERROR=%r" % (e,))
        pass

    return e_code


#---------------------------------------------

def launch():

    from argparse import ArgumentParser

    parser = ArgumentParser(description='Run file cheking service.')
    parser.add_argument('--host', dest='host', action='store', type=str,
        help='Listen on TCP address')
    parser.add_argument('--port', dest='port', action='store', type=int,
        help='Listen on TCP port')
    parser.add_argument('--task-service', dest='task_service', action='store', type=str,
        help='Task service listen on TCP address')

    args = parser.parse_args()
    return run_service(args.host, args.port, args.task_service)

if __name__ == "__main__":
    sys.exit(launch())
